"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTransport = exports.listen = void 0;
const fs_1 = __importDefault(require("fs"));
const os_1 = __importDefault(require("os"));
const http_1 = __importDefault(require("http"));
const path_1 = __importDefault(require("path"));
const cors_1 = __importDefault(require("cors"));
const express_1 = __importDefault(require("express"));
const dotenv_1 = __importDefault(require("dotenv"));
const node_os_utils_1 = __importDefault(require("node-os-utils"));
const core_1 = require("@colyseus/core");
const ws_transport_1 = require("@colyseus/ws-transport");
// try to import uWebSockets-express compatibility layer.
let uWebSocketsExpressCompatibility;
try {
    uWebSocketsExpressCompatibility = require('uwebsockets-express').default;
}
catch (e) { }
function getNodeEnv() {
    return process.env.NODE_ENV || "development";
}
function getRegion() {
    // EU, NA, AS, AF, AU, SA, UNKNOWN
    return (process.env.REGION || "unknown").toLowerCase();
}
function loadEnvFile(envFileOptions, log = 'none') {
    const envPaths = [];
    envFileOptions.forEach((envFilename) => {
        var _a;
        envPaths.push(path_1.default.resolve(path_1.default.dirname(((_a = require === null || require === void 0 ? void 0 : require.main) === null || _a === void 0 ? void 0 : _a.filename) || process.cwd()), "..", envFilename));
        envPaths.push(path_1.default.resolve(process.cwd(), envFilename));
    });
    // return the first .env path found
    const envPath = envPaths.find((envPath) => fs_1.default.existsSync(envPath));
    if (envPath) {
        dotenv_1.default.config({ path: envPath });
        if (log !== "none") {
            core_1.logger.info(`✅ ${path_1.default.basename(envPath)} loaded.`);
        }
    }
    else if (log === "both") {
        core_1.logger.info(`ℹ️  optional .env file not found: ${envFileOptions.join(", ")}`);
    }
}
// load .env.cloud defined on admin panel
if (process.env.COLYSEUS_CLOUD !== undefined) {
    loadEnvFile([`.env.cloud`]);
}
// (overrides previous env configs)
loadEnvFile([`.env.${getNodeEnv()}`, `.env`], 'both');
if (process.env.REGION !== undefined) {
    loadEnvFile([`.env.${getRegion()}.${getNodeEnv()}`], 'success');
}
const ALLOWED_KEYS = {
    'displayLogs': "boolean",
    'options': "object",
    'getId': "function",
    'initializeTransport': "function",
    'initializeExpress': "function",
    'initializeGameServer': "function",
    'beforeListen': "function"
};
function default_1(options) {
    for (const option in options) {
        //@ts-ignore
        if (!ALLOWED_KEYS[option]) {
            throw new Error(`❌ Invalid option '${option}'. Allowed options are: ${Object.keys(ALLOWED_KEYS).join(", ")}`);
        }
        //@ts-ignore
        if (options[option] !== undefined && typeof (options[option]) !== ALLOWED_KEYS[option]) {
            //@ts-ignore
            throw new Error(`❌ Invalid type for ${option}: please provide a ${ALLOWED_KEYS[option]} value.`);
        }
    }
    return options;
}
exports.default = default_1;
/**
 * Listen on your development environment
 * @param options Application options
 * @param port Port number to bind Colyseus + Express
 */
function listen(options, port = Number(process.env.PORT || 2567)) {
    var _a, _b, _c;
    return __awaiter(this, void 0, void 0, function* () {
        const serverOptions = options.options || {};
        options.displayLogs = (_a = options.displayLogs) !== null && _a !== void 0 ? _a : true;
        // Force 2567 port on Colyseus Cloud
        if (process.env.COLYSEUS_CLOUD !== undefined) {
            port = 2567;
        }
        //
        // Handling multiple processes
        // Use NODE_APP_INSTANCE to play nicely with pm2
        //
        const processNumber = Number(process.env.NODE_APP_INSTANCE || "0");
        port += processNumber;
        // automatically configure for production under Colyseus Cloud
        if (process.env.COLYSEUS_CLOUD !== undefined) {
            // special configuration is required when using multiple processes
            const isMultiProcess = (os_1.default.cpus().length > 1);
            if (!serverOptions.driver && isMultiProcess) {
                let RedisDriver = undefined;
                try {
                    RedisDriver = require('@colyseus/redis-driver').RedisDriver;
                    serverOptions.driver = new RedisDriver();
                }
                catch (e) {
                    core_1.logger.warn("");
                    core_1.logger.warn("❌ coult not initialize RedisDriver.");
                    core_1.logger.warn("👉 npm install --save @colyseus/redis-driver");
                    core_1.logger.warn("");
                }
            }
            if (!serverOptions.presence && isMultiProcess) {
                let RedisPresence = undefined;
                try {
                    RedisPresence = require('@colyseus/redis-presence').RedisPresence;
                    serverOptions.presence = new RedisPresence();
                }
                catch (e) {
                    core_1.logger.warn("");
                    core_1.logger.warn("❌ coult not initialize RedisPresence.");
                    core_1.logger.warn("👉 npm install --save @colyseus/redis-presence");
                    core_1.logger.warn("");
                }
            }
            // force "publicAddress" when deployed on "Colyseus Cloud".
            serverOptions.publicAddress = process.env.SUBDOMAIN + "." + process.env.SERVER_NAME;
            // nginx is responsible for forwarding /{port}/ to this process
            if (isMultiProcess) {
                serverOptions.publicAddress += "/" + port;
            }
        }
        const transport = yield getTransport(options);
        const gameServer = new core_1.Server(Object.assign(Object.assign({}, serverOptions), { transport }));
        yield ((_b = options.initializeGameServer) === null || _b === void 0 ? void 0 : _b.call(options, gameServer));
        yield ((_c = options.beforeListen) === null || _c === void 0 ? void 0 : _c.call(options));
        if (process.env.COLYSEUS_CLOUD !== undefined) {
            // listening on socket
            // @ts-ignore
            yield gameServer.listen(`/run/colyseus/${port}.sock`);
        }
        else {
            // listening on port
            yield gameServer.listen(port);
        }
        // notify process manager (production)
        if (typeof (process.send) === "function") {
            process.send('ready');
        }
        if (options.displayLogs) {
            core_1.logger.info(`⚔️  Listening on http://localhost:${port}`);
        }
        return gameServer;
    });
}
exports.listen = listen;
function getTransport(options) {
    return __awaiter(this, void 0, void 0, function* () {
        let transport;
        if (!options.initializeTransport) {
            // use WebSocketTransport by default
            options.initializeTransport = (options) => new ws_transport_1.WebSocketTransport(options);
        }
        let app = (0, express_1.default)();
        let server = http_1.default.createServer(app);
        transport = yield options.initializeTransport({ server });
        if (options.initializeExpress) {
            // uWebSockets.js + Express compatibility layer.
            // @ts-ignore
            if (transport['app']) {
                if (typeof (uWebSocketsExpressCompatibility) === "function") {
                    if (options.displayLogs) {
                        core_1.logger.info("✅ uWebSockets.js + Express compatibility enabled");
                    }
                    // @ts-ignore
                    server = undefined;
                    // @ts-ignore
                    app = uWebSocketsExpressCompatibility(transport['app']);
                }
                else {
                    if (options.displayLogs) {
                        core_1.logger.warn("");
                        core_1.logger.warn("❌ uWebSockets.js + Express compatibility mode couldn't be loaded, run the following command to fix:");
                        core_1.logger.warn("👉 npm install --save uwebsockets-express");
                        core_1.logger.warn("");
                    }
                    app = undefined;
                }
            }
        }
        if (app) {
            // Enable CORS + JSON parsing.
            app.use((0, cors_1.default)());
            app.use(express_1.default.json());
            if (options.initializeExpress) {
                yield options.initializeExpress(app);
            }
            app.get("/__cloudstats", (req, res) => __awaiter(this, void 0, void 0, function* () {
                if (process.env.CLOUD_SECRET &&
                    req.headers.authorization !== process.env.CLOUD_SECRET) {
                    res.status(401).end();
                    return;
                }
                const roomCountPerProcess = yield core_1.matchMaker.presence.hgetall("roomcount");
                let rooms = 0;
                for (const processId in roomCountPerProcess) {
                    rooms += Number(roomCountPerProcess[processId]);
                }
                const ccu = yield core_1.matchMaker.presence.get("_ccu");
                const mem = yield node_os_utils_1.default.mem.used();
                const cpu = (yield node_os_utils_1.default.cpu.usage()) / 100;
                res.json({
                    version: 1,
                    mem: (mem.usedMemMb / mem.totalMemMb),
                    cpu,
                    ccu,
                    rooms,
                });
            }));
            if (options.displayLogs) {
                core_1.logger.info("✅ Express initialized");
            }
        }
        return transport;
    });
}
exports.getTransport = getTransport;
